/**
 * @file       st7789.c
 * @author     wangbing
 * @email      mz8023yt@163.com
 * @version    V1.0
 * @date       2022-03-08
 * @brief      ips lcd panel drvier
 */

#include "st7789.h"
#include <string.h>

/**
 * @brief 将 16bit 命令或者数据, 去除高7无效比特, 转化为 9bit 后写入 LCD
 *
 * @param lcd
 * @param bytes
 * @param len
 *
 * @note 此接口适用于 9bit spi 通信
 */
void st7789_16bit_write(struct st7789* lcd, uint16_t* bytes, uint32_t len)
{
	int i = 0;
	int r = 0;
	int g = 0;
	int next = 0;
	int offset = 0;
	int size = 0;
	uint8_t* buf = NULL;

	if (len % 8 == 0)
		size = len / 8 * 9;
	else
		size = (len / 8 + 1) * 9;

	buf = malloc(size);
	memset(buf, 0, size);

	for (i = 0; i < len; i++) {
		r = i % 8;
		g = i / 8;
		next = r + 1;
		offset = 8 - next;
		buf[g + i] |= (bytes[i] >> next);
		buf[g + i + 1] |= (bytes[i] << offset);
	}

	lcd->ops.write(buf, size);
	free(buf);
}

/**
 * @brief 将 8bit 的数据, 添加前缀 1b, 转化为 9bit 后写入 LCD
 *
 * @param lcd
 * @param bytes
 * @param len
 *
 * @note 此接口适用于 9bit spi 通信
 */
void st7789_8bit_data_write(struct st7789* lcd, uint8_t* bytes, uint32_t len)
{
	int i = 0;
	int ilen = 0;
	int olen = 0;
	int fill = 0;
	uint8_t* ibuf = NULL;
	uint8_t* obuf = NULL;

	ilen = len;
	olen = (ilen + 7) / 8 * 9;
	fill = 8 - ilen % 8;
	if (8 == fill)
		fill = 0;

	memset(bytes + ilen, 0, olen - ilen);

	ibuf = bytes + (ilen + 7) / 8 * 8;
	obuf = bytes + olen;
	while (obuf > bytes && ibuf > bytes) {
		obuf -= 9;
		ibuf -= 8;
		obuf[8] = ibuf[7];
		obuf[7] = ibuf[6] << 1 | 1 << 0;
		obuf[6] = ibuf[5] << 2 | 1 << 1 | ibuf[6] >> 7;
		obuf[5] = ibuf[4] << 3 | 1 << 2 | ibuf[5] >> 6;
		obuf[4] = ibuf[3] << 4 | 1 << 3 | ibuf[4] >> 5;
		obuf[3] = ibuf[2] << 5 | 1 << 4 | ibuf[3] >> 4;
		obuf[2] = ibuf[1] << 6 | 1 << 5 | ibuf[2] >> 3;
		obuf[1] = ibuf[0] << 7 | 1 << 6 | ibuf[1] >> 2;
		obuf[0] = 1 << 7 | ibuf[0] >> 1;
	}

	obuf = bytes + olen - 9;
	for (i = 0; i < fill; i++) {
		obuf[7 - i] &= ~((i + 1) << 0);
	}

	obuf = bytes;
	lcd->ops.write(obuf, olen);
}

void st7789_init(struct st7789* lcd)
{
	/* reset sequence */
	/* the spec requires at least 10us */
	lcd->ops.reset(LOW);
	lcd->ops.msleep(100);

	/* the spec requires a maximum wait of 120ms */
	lcd->ops.reset(HIGH);
	lcd->ops.msleep(120);

	/* initializing the lcd */
	if (9 == lcd->cfg.fix.bits) {
		/* initialization commands list */
		uint16_t init_cmds_9bit[] = {
			0x036, 0x100, /* format */
			0x021,        /* pixel inversion */
			0x03a, 0x105,
			0x0b2, 0x10c, 0x10c, 0x100, 0x133, 0x133,
			0x0b7, 0x135,
			0x0bb, 0x11f, /* power */
			0x0c0, 0x12c,
			0x0c2, 0x101,
			0x0c3, 0x11d,
			0x0c4, 0x120,
			0x0c6, 0x10f,
			0x0d0, 0x1a4, 0x1a1,
			0x0E4, 0x127, 0x100, 0x110,
			0x0e0, 0x1d0, 0x100, 0x114, 0x115, 0x113, 0x12c, 0x142, 0x143, 0x14e, 0x109, 0x116, 0x114, 0x118, 0x121,
			0x0e1, 0x1d0, 0x100, 0x114, 0x115, 0x113, 0x10b, 0x143, 0x155, 0x153, 0x10c, 0x117, 0x114, 0x123, 0x120,
			0x011, /* exit sleep mode */
			0x029, /* display on */
		};

		st7789_16bit_write(lcd, init_cmds_9bit, sizeof(init_cmds_9bit) / 2);
	} else {
		uint32_t n = 0;
		struct cmds init_cmds_8bit[] = {
			{ 0x36, { 0x00 }, 1 },
			{ 0x21, { 0 }, 0 },
			{ 0x3a, { 0x05 }, 1 },
			{ 0xb2, { 0x0c, 0x0c, 0x00, 0x33, 0x33 }, 5 },
			{ 0xb7, { 0x35 }, 1 },
			{ 0xbb, { 0x1f }, 1 },
			{ 0xc0, { 0x2c }, 1 },
			{ 0xc2, { 0x01 }, 1 },
			{ 0xc3, { 0x1d }, 1 },
			{ 0xc4, { 0x20 }, 1 },
			{ 0xc6, { 0x0f }, 1 },
			{ 0xd0, { 0xa4, 0xa1 }, 2 },
			{ 0xe4, { 0x27, 0x00, 0x00 }, 3 },
			{ 0xe0, { 0xd0, 0x00, 0x14, 0x15, 0x13, 0x2c, 0x42, 0x43, 0x4e, 0x09, 0x16, 0x14, 0x18, 0x21 }, 14 },
			{ 0xe1, { 0xd0, 0x00, 0x14, 0x15, 0x13, 0x0b, 0x43, 0x55, 0x53, 0x0c, 0x17, 0x14, 0x23, 0x20 }, 14 },
			{ 0x11, { 0 }, 0 },
			{ 0x29, { 0 }, 0 },
			{ 0x00, { 0 }, 0xff },
		};

		while (init_cmds_8bit[n].nums != 0xff) {
			lcd->ops.datacmd(LOW);
			lcd->ops.write(&init_cmds_8bit[n].cmd, 1);

			if (!init_cmds_8bit[n].nums) {
				n++;
				continue;
			}

			lcd->ops.datacmd(HIGH);
			lcd->ops.write(init_cmds_8bit[n].dats, init_cmds_8bit[n].nums);
			n++;
		}
	}

	/* light up backlight */
	lcd->ops.backlight(HIGH);
}

void st7789_point_draw(struct st7789* lcd, uint16_t x, uint16_t y, uint16_t color)
{
	uint8_t xh = 0;
	uint8_t xl = 0;
	uint8_t yh = 0;
	uint8_t yl = 0;
	uint8_t ch = 0;
	uint8_t cl = 0;

	x = x + lcd->cfg.fix.xshift;
	y = y + lcd->cfg.fix.yshift;
	xh = x >> 8;
	xl = x & 0xff;
	yh = y >> 8;
	yl = y & 0xff;
	ch = color >> 8;
	cl = color;

	if (9 == lcd->cfg.fix.bits) {
		uint8_t point_draw_cmds[18] = {
			0x15, 0x40, 0x20, 0x10, 0x08, 0x00, 0xae, 0x01, 0x00,
			0x80, 0x40, 0x05, 0x90, 0x08, 0x00, 0x00, 0x00, 0x00
		};

		point_draw_cmds[1] |= xh >> 2;
		point_draw_cmds[2] |= xh << 6 | xl >> 3;
		point_draw_cmds[3] |= xl << 5 | xh >> 4;
		point_draw_cmds[4] |= xh << 4 | xl >> 5;
		point_draw_cmds[5] |= xl << 3;
		point_draw_cmds[6] |= yh >> 7;
		point_draw_cmds[7] |= yh << 1;
		point_draw_cmds[8] |= yl;
		point_draw_cmds[9] |= yh >> 1;
		point_draw_cmds[10] |= yh << 7 | yl >> 2;
		point_draw_cmds[11] |= yl << 6;
		point_draw_cmds[12] |= ch >> 4;
		point_draw_cmds[13] |= ch << 4 | cl >> 5;
		point_draw_cmds[14] |= cl << 3;
		lcd->ops.write(point_draw_cmds, sizeof(point_draw_cmds));
	} else {
		uint8_t cmds = 0x00;
		uint8_t dats[4] = { 0x00, 0x00, 0x00, 0x00 };

		/* Column Address Set */
		cmds = 0x2A;
		dats[0] = xh;
		dats[1] = xl;
		dats[2] = xh;
		dats[3] = xl;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 4);

		/* Row Address Set */
		cmds = 0x2B;
		dats[0] = yh;
		dats[1] = yl;
		dats[2] = yh;
		dats[3] = yl;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 4);

		/* Memory Write */
		cmds = 0x2C;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		dats[0] = ch;
		dats[1] = cl;
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 2);
	}
}

void st7789_rect_fill(struct st7789* lcd, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t* color)
{
	if (9 == lcd->cfg.fix.bits) {
		int w = 0;
		int h = 0;
		int align = 5;
		uint16_t area_set_cmds[] = {
			0x000, 0x000, 0x000, 0x000, 0x000, // 8byte align
			0x02a, 0x100, 0x100, 0x100, 0x100, // column address set
			0x02b, 0x100, 0x100, 0x100, 0x100, // row address set
			0x02c,                             // memory write
		};
		y1 = y1 + lcd->cfg.fix.yshift;
		y2 = y2 + lcd->cfg.fix.yshift;
		area_set_cmds[align + 1] |= ((x1 >> 8) & 0xff);
		area_set_cmds[align + 2] |= (x1 & 0xff);
		area_set_cmds[align + 3] |= ((x2 >> 8) & 0xff);
		area_set_cmds[align + 4] |= (x2 & 0xff);
		area_set_cmds[align + 6] |= ((y1 >> 8) & 0xff);
		area_set_cmds[align + 7] |= (y1 & 0xff);
		area_set_cmds[align + 8] |= ((y2 >> 8) & 0xff);
		area_set_cmds[align + 9] |= (y2 & 0xff);
		st7789_16bit_write(lcd, area_set_cmds, sizeof(area_set_cmds) / 2);

		w = x2 - x1 + 1;
		h = y2 - y1 + 1;
		st7789_8bit_data_write(lcd, (uint8_t*)color, w * h * 2);
	} else {
		uint8_t cmds = 0x00;
		uint8_t dats[4] = { 0x00, 0x00, 0x00, 0x00 };
		uint32_t size = 0;

		/* Column Address Set */
		cmds = 0x2A;
		dats[0] = (x1 + lcd->cfg.fix.xshift) >> 8;
		dats[1] = (x1 + lcd->cfg.fix.xshift) & 0xff;
		dats[2] = (x2 + lcd->cfg.fix.xshift) >> 8;
		dats[3] = (x2 + lcd->cfg.fix.xshift) & 0xff;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 4);

		/* Row Address Set */
		cmds = 0x2B;
		dats[0] = (y1 + lcd->cfg.fix.yshift) >> 8;
		dats[1] = (y1 + lcd->cfg.fix.yshift) & 0xff;
		dats[2] = (y2 + lcd->cfg.fix.yshift) >> 8;
		dats[3] = (y2 + lcd->cfg.fix.yshift) & 0xff;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 4);

		/* Memory Write */
		cmds = 0x2C;
		size = (x2 - x1 + 1) * (y2 - y1 + 1) * 2;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write((uint8_t*)color, size);
	}
}

void st7789_rect_fill_color(struct st7789* lcd, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
	if (8 == lcd->cfg.fix.bits) {
		int i = 0;
		uint8_t cmds = 0x00;
		uint8_t dats[4] = { 0x00, 0x00, 0x00, 0x00 };
		uint32_t cnts = 0;

		/* Column Address Set */
		cmds = 0x2A;
		dats[0] = (x1 + lcd->cfg.fix.xshift) >> 8;
		dats[1] = (x1 + lcd->cfg.fix.xshift) & 0xff;
		dats[2] = (x2 + lcd->cfg.fix.xshift) >> 8;
		dats[3] = (x2 + lcd->cfg.fix.xshift) & 0xff;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 4);

		/* Row Address Set */
		cmds = 0x2B;
		dats[0] = (y1 + lcd->cfg.fix.yshift) >> 8;
		dats[1] = (y1 + lcd->cfg.fix.yshift) & 0xff;
		dats[2] = (y2 + lcd->cfg.fix.yshift) >> 8;
		dats[3] = (y2 + lcd->cfg.fix.yshift) & 0xff;
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		lcd->ops.write(dats, 4);

		/* Memory Write */
		cmds = 0x2C;
		cnts = (x2 - x1 + 1) * (y2 - y1 + 1);
		lcd->ops.datacmd(LOW);
		lcd->ops.write(&cmds, 1);
		lcd->ops.datacmd(HIGH);
		for (i = 0; i < cnts; i++) {
			dats[0] = color >> 8;
			dats[1] = color & 0xff;
			lcd->ops.write(dats, 2);
		}
	}
}
